//
// Created by hunan on 2025/10/1.
//

#include "hls_download_task.h"

#include "public_module/file_module/file_errno.h"
#include "public_module/file_module/file_handle.h"
#include "public_module/hls_protocol/hls_fragment.h"
#include "public_module/hls_protocol/hls_m3u8_message.h"
#include "public_module/http_protocol/http_protocol_msg.h"
#include "public_module/log_module/log_public_module.h"
#include "public_module/loop_module/loop.h"
#include "public_module/openssl_util/openssl_util.h"
#include "public_module/protocol_connection/hls_protocol_operation.h"
#include "public_module/protocol_connection/protocol_connection.h"
#include "public_module/protocol_connection/protocol_connection_pool.h"
#include "public_module/protocol_connection/protocol_errno.h"

HlsDownloadTask::HlsDownloadTask(
     const std::string & url,
     const std::string & m3u8_output,
     const std::string & fragment_output_path,
     int   connection_num)
{
    m_url = url;
    m_m3u8_output = m3u8_output;
    m_fragment_output_path = fragment_output_path;
    m_connection_num = connection_num;
    m_loop      = new Loop();
    m_conn_pool = std::make_shared<ProtocolConnectionPool>(m_loop);
    m_hls_conn   = std::make_shared<ProtocolConnectionPool>(m_loop);
}

void HlsDownloadTask::StartDownload()
{
    auto pos = m_m3u8_output.rfind('/');
    if (pos == std::string::npos)
    {
        m_fragment_full_path = m_fragment_output_path + "/";
    }
    else
    {
        m_fragment_full_path = m_m3u8_output.substr(0, pos + 1) + m_fragment_output_path + "/";
    }
    FileHandleUtil::MakeDir(m_fragment_output_path);
    HttpProtocolOperationFactory factory;
    HlsProtocolOperationFactory  http_factory;
    m_conn_pool->InitProtocolConnectionPool(m_connection_num, &factory);
    m_hls_conn->InitProtocolConnectionPool(1, &http_factory);
    auto connection = m_hls_conn->GetConnection();
    connection->OnProtocolConnect([](auto conn, int result) {

        if (result == EmProtocolErrno::EmProtocolErrno_SUCCESS)
        {
            auto operate = conn->GetProtocolOperation();
            auto hls_operate = static_cast<HlsProtocolOperation *>(operate);
            auto http_operate = hls_operate->HttpProtocolOperate();
            auto msg = http_operate->HttpRequestMsg();
            msg->AddHeaderLine("GET", conn->ProtocolData().m_uri, "HTTP/1.1");
            msg->AddHeaderSign("Host", conn->ProtocolData().m_host);
            std::string content;
            msg->String(content);
            conn->SendMessage(reinterpret_cast<const uint8_t *>(content.c_str()), content.size());

        }
    });
    connection->OnProtocolMessage([this](auto conn, auto operate) {

        auto hls_operate = static_cast<HlsProtocolOperation *>(operate);
        LOG_PUBLIC_MODULE("hls operate success:%d", hls_operate->OpSuccess());
        if (hls_operate->OpSuccess())
        {
            auto msg = hls_operate->M3U8Msg();
            if (!msg->IncludeStreamInfo())
            {
                m_hls_fragment = std::make_shared<HlsFragment>(msg);
                FileHandle handle(m_m3u8_output);
                handle.OpenFileReadWrite();
                std::string content;
                HlsM3U8MessageUtil::BuildM3U8Content(hls_operate->M3U8Msg(), m_fragment_output_path, content);
                handle.Write(reinterpret_cast<const uint8_t *>(content.c_str()), static_cast<int>(content.size()));
                StartDownloadFragment();
            }
            else
            {
                LOG_PUBLIC_MODULE("include media stream info, please select a playlist url to download");
                auto & uri_vec = msg->UriIndex();
                auto & stream_info = msg->StreamInfoData();
                size_t length = uri_vec.size() > stream_info.size() ? stream_info.size() : uri_vec.size();
                std::string url;
                if (m_url.find("https://") != std::string::npos || m_url.find("http://") != std::string::npos)
                {
                    auto pos = m_url.rfind('/');
                    url = m_url.substr(0, pos + 1);
                }
                for (size_t index = 0; index < length; ++index)
                {
                    LOG_PUBLIC_MODULE("resolution:%s,url:%s%s", stream_info[index].m_resolution.c_str(), url.c_str(), uri_vec[index].m_index_uri.c_str());
                }
                Stop();
            }
        }
    });
    connection->OnProtocolDisConn([](auto conn, int result) {

    });
    if (connection->InitServerUrl(m_url) < EmProtocolErrno::EmProtocolErrno_SUCCESS)
    {
        return;
    }
    m_loop->Run();
}

void HlsDownloadTask::Stop() const
{
    m_conn_pool->CloseConnection();
    m_hls_conn->CloseConnection();
}

void HlsDownloadTask::StartDownloadFragment()
{
    for (int index = 0; index < m_connection_num; ++index)
    {
        auto connection = m_conn_pool->GetConnection();
        if (!connection)
        {
            break;
        }
        m_conn_pool->ReleaseConnection(connection);
        DownloadFragment();
    }
}

void HlsDownloadTask::ExecuteDownloadFragment(
    int index,
    const std::string & uri,
    const std::shared_ptr<ProtocolConnection>& connection)
{
    std::string filename = m_fragment_full_path + uri;
    std::string md5_value;
    if (OpenSSLUtil::ComputeFileMD5(filename, md5_value) == -1)
    {
        auto * http_operate = dynamic_cast<HttpProtocolOperation *>(connection->GetProtocolOperation());
        auto msg = http_operate->HttpRequestMsg();
        msg->AddHeaderLine("GET", connection->ProtocolData().m_uri, "HTTP/1.1");
        msg->AddHeaderSign("Host", connection->ProtocolData().m_host);
        std::string content;
        msg->String(content);
        connection->SendMessage(reinterpret_cast<const uint8_t *>(content.c_str()), static_cast<int>(content.size()));
        connection->OnProtocolMessage([this, connection, filename, index](auto conn, auto response_operate) {

            auto http_operate = static_cast<HttpProtocolOperation *>(response_operate);
            auto response = http_operate->HttpResponseMsg();
            if (response->GetCode() == "200")
            {
                FileHandle handle(filename);
                handle.OpenFileReadWrite();
                auto & body = response->Body();
                handle.Write(body.data(), static_cast<int>(body.size()));
                m_hls_fragment->UpdateFragmentSuccess(index);
            }
            else
            {
                m_hls_fragment->UpdateFragmentFailed(index);
            }
            http_operate->ClearMsg();
            m_conn_pool->ReleaseConnection(connection);
            DownloadFragment();
        });
    }
    else
    {
        auto http_operate = dynamic_cast<HttpProtocolOperation *>(connection->GetProtocolOperation());
        auto msg = http_operate->HttpRequestMsg();
        msg->AddHeaderLine("HEAD", connection->ProtocolData().m_uri, "HTTP/1.1");
        msg->AddHeaderSign("Host", connection->ProtocolData().m_host);
        std::string content;
        msg->String(content);
        connection->SendMessage(reinterpret_cast<const uint8_t *>(content.c_str()), static_cast<int>(content.size()));
        connection->OnProtocolMessage([this, connection, index, filename, md5_value](auto conn, auto response_operate) {

            auto http_operate = static_cast<HttpProtocolOperation *>(response_operate);
            auto response = http_operate->HttpResponseMsg();
            auto etag = response->GetHeaderValue("ETag");
            LOG_PUBLIC_MODULE("HlsDownloadTask(%p) ExecuteDownloadFragment connection(%p) compare md5 etag(%s) file(%s)", this, conn, etag.c_str(), md5_value.c_str());
            if (response->GetCode() == "200" && !etag.empty() && etag.find(md5_value) != std::string::npos)
            {
                m_hls_fragment->UpdateFragmentSuccess(index);
            }
            else
            {
                FileHandle handle(filename);
                handle.OpenFileReadWrite();
                if (handle.DeleteFile() != EmFileErrno::EmFileErrno_SUCCESS)
                {
                    LOG_PUBLIC_MODULE("HlsDownloadTask(%p) ExecuteDownloadFragment Delete file name(%s) failed", this, filename.c_str());
                }
                m_hls_fragment->UpdateFragmentFailed(index);
            }
            http_operate->ClearMsg();
            m_conn_pool->ReleaseConnection(connection);
            DownloadFragment();
        });
    }
    connection->OnProtocolDisConn([this, connection](auto conn, int result) {

        auto http_operate = static_cast<HttpProtocolOperation *>(conn->GetProtocolOperation());
        http_operate->ClearMsg();
        m_conn_pool->ReleaseConnection(connection);
    });
}

void HlsDownloadTask::DownloadFragment()
{
    auto connection = m_conn_pool->GetConnection();
    if (!connection)
    {
        return;
    }
    int index = 0;
    std::string uri = m_hls_fragment->GetOneFragment(index);
    if (index == -1)
    {
        if (m_hls_fragment->AllFragmentSuccess())
        {
            m_conn_pool->ReleaseConnection(connection);
            m_conn_pool->CloseConnection();
            m_hls_conn->CloseConnection();
            LOG_PUBLIC_MODULE("HlsDownloadTask(%p) DownloadFragment download finish", this);
        }
        return;
    }
    LOG_PUBLIC_MODULE("HlsDownloadTask(%p) DownloadFragment connection(%p) uri(%s)", this, connection.get(), uri.c_str());
    std::string url;
    if (m_url.find("https://") != std::string::npos || m_url.find("http://") != std::string::npos)
    {
        auto pos = m_url.rfind('/');
        url = m_url.substr(0, pos + 1) + uri;
    }
    else
    {
        url = uri;
    }
    LOG_PUBLIC_MODULE("HlsDownloadTask(%p) DownloadFragment connection(%p) Download url:%s", this, connection.get(), url.c_str());
    connection->OnProtocolConnect([this, connection, index, uri](auto conn, auto operate) {
        if (operate == EmProtocolErrno_SUCCESS)
        {
            ExecuteDownloadFragment(index, uri, connection);
        }
    });
    int result = connection->InitServerUrl(url);
    if (result != EmProtocolErrno_CONNECTED && result != EmProtocolErrno_SUCCESS)
    {
        m_hls_fragment->UpdateFragmentFailed(index);
        m_conn_pool->ReleaseConnection(connection);
    }
    else
    {
        if (result == EmProtocolErrno_CONNECTED)
        {
            ExecuteDownloadFragment(index, uri, connection);
        }
    }
}



